Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the option to support multiple and overrideable programs per cgroup #985

Merged
merged 1 commit into from
Sep 5, 2024

Conversation

reyzell
Copy link
Contributor

@reyzell reyzell commented Jul 10, 2024

Add the option to support multiple and overrideable programs per cgroup

This change allows multiple BPF programs to attach to a cgroup (via the option
CgroupAttachMode::AllowMultiple), and allows a program to specify that it can be
overridden by one in a sub-cgroup (via the option CgroupAttachMode::AllowOverride).

Copy link

netlify bot commented Jul 10, 2024

Deploy Preview for aya-rs-docs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit f790685
🔍 Latest deploy log https://app.netlify.com/sites/aya-rs-docs/deploys/66d82c4ab20ebd0008b90b48
😎 Deploy Preview https://deploy-preview-985--aya-rs-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@mergify mergify bot added the aya This is about aya (userspace) label Jul 10, 2024
@@ -68,7 +68,8 @@ impl LircMode2 {
let prog_fd = prog_fd.try_clone()?;
let lircdev_fd = lircdev.as_fd().try_clone_to_owned()?;

bpf_prog_attach(prog_fd.as_fd(), lircdev_fd.as_fd(), BPF_LIRC_MODE2)?;
const NO_FLAGS : u32 = 0;
bpf_prog_attach(prog_fd.as_fd(), lircdev_fd.as_fd(), BPF_LIRC_MODE2, NO_FLAGS)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per comment below this would become CgroupAttachFlags::default()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the fn signature to allow flags to be passed in, and updated doc tests to pass in CgroupAttachFlags::empty(). Let me know if that's what you had in mind.

bitflags::bitflags! {
/// Flags passed to [`SockOps::attach()`].
#[derive(Clone, Copy, Debug, Default)]
pub struct SockOpsFlags: u32 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't just SockOps flags though.
See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/bpf.h#L1153

This should probably live in links.rs and be called CgroupAttachFlags.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Done.

Self::attach_with_flags(prog_fd, target_fd, attach_type, 0)
}

pub(crate) fn attach_with_flags(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer changing the API here to add flags: u32 instead of adding a new funciton.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, done.

/// Attaches the program to the given cgroup.
///
/// The returned value can be used to detach, see [SockOps::detach].
pub fn attach_with_flags<T: AsFd>(&mut self, cgroup: T, flags: SockOpsFlags) -> Result<SockOpsLinkId, ProgramError> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather break the API here and add flags: CgroupAttachFlags.
This makes it explicit which flags are being used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I wasn't sure how much of an expanding change you'd prefer. I've modified the attach() fn signature here and for other program types.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it explicit which flags are being used.

I'm in two minds about this. For the Cgroup* and SockOps programs, I think it makes sense to have to pass CgroupAttachFlags to attach() - you're explicitly working with cgroups, so it makes sense to have to think about what's going on with other cgroups.

But for SkMsg SkSkb and LircMode2, I think we should default to no flags like before, and have a separate attach_with_flags() for the cases in which you do care about cgroups.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a keen observation. I've updated this change following your advice to extend attach() for specific program types, and adding attach_with_flags() where the developer does not necessarily need to think about cgroups.

let prog_fd = self.fd()?;

let link = ProgAttachLink::attach(prog_fd.as_fd(), cgroup.as_fd(), BPF_CGROUP_SOCK_OPS)?;
let link = ProgAttachLink::attach_with_flags(prog_fd.as_fd(), cgroup.as_fd(), BPF_CGROUP_SOCK_OPS, flags.bits())?;
Copy link
Member

@dave-tucker dave-tucker Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside: Given all the other Cgroup programs can be attached with bpf_link - see:

if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {

I'd wager that this is likely the case for sock_ops programs too. I'll open an issue for that.
#987

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@reyzell reyzell changed the title Add support for multiple sock_ops programs per cgroup Add support for multiple programs per cgroup Jul 12, 2024
@reyzell
Copy link
Contributor Author

reyzell commented Jul 16, 2024

Hello, I'm just following up on the status of this one.

Copy link
Member

@dave-tucker dave-tucker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there. See notes on the other generic flags you included in the CgroupAttachFlags.
I'm fine to have them there if you've tested that they work as expected...

I'd be genuinely surprised if all of the flags are honoured by every program type that attaches to cgroups.

I would recommend dropping them for the sake of simplicity 😆
Otherwise, the generic flags might be better off as struct GenericAttachFlags it's been a while since I looked at bitflags but it would be nice to express that the flags could be either CgroupAttachFlags or GenericAttachFlags

@@ -28,6 +31,25 @@ pub trait Link: std::fmt::Debug + 'static {
fn detach(self) -> Result<(), ProgramError>;
}

bitflags::bitflags! {
/// Flags passed to [`SockOps::attach()`].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc comment is now inaccurate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, true. I've switched it to the more general comment of "Program attachment flags." Let me know if you'd like a more descriptive comment.

/// Allows multiple eBPF programs to be attached.
const ALLOW_MULTI = BPF_F_ALLOW_MULTI;
/// Replaces an existing attachment.
const REPLACE = BPF_F_REPLACE;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tested this works as expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the Testing comment I've added below. This covers the two flags that have been kept in this PR.

/// Replaces an existing attachment.
const REPLACE = BPF_F_REPLACE;
/// Attaches the program so as to be executed first for the cgroup.
const BEFORE = BPF_F_BEFORE;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this one

/// Attaches the program so as to be executed first for the cgroup.
const BEFORE = BPF_F_BEFORE;
/// Attaches the program so as to be executed last for the cgroup.
const AFTER = BPF_F_AFTER;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this one too

/// Attaches the program so as to be executed last for the cgroup.
const AFTER = BPF_F_AFTER;
/// Indicates the eBPF program is referred to by its ID.
const ID = BPF_F_ID;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wouldn't make sense to include anyway since we use FD, not ID

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I've removed these additional flags, since ALLOW_MULTI and ALLOW_OVERRIDE were the focus of my change.

@reyzell
Copy link
Contributor Author

reyzell commented Jul 25, 2024

Almost there. See notes on the other generic flags you included in the CgroupAttachFlags. I'm fine to have them there if you've tested that they work as expected...

I'd be genuinely surprised if all of the flags are honoured by every program type that attaches to cgroups.

I would recommend dropping them for the sake of simplicity 😆

Ha, so what's funny is on the fork of Aya I started with internally, I added only the ALLOW_OVERRIDE and ALLOW_MULTI flags. I included the full set of flags in this pull request because I assumed you'd want a more fully-featured change. Bad guess on my part. I've now paired this down to just the two flags.

@reyzell
Copy link
Contributor Author

reyzell commented Jul 25, 2024

Testing

I tested with the application I'm developing by attaching the program twice to a cgroup and kicking off some network chatter. This was performed under three different conditions: 1) before this change (i.e. using the ::empty() flag), 2) using flag ALLOW_OVERRIDE, and 3) using flag ALLOW_MULTI. The output logged from the application showed the multiple invocations of the BPF programs in ALLOW_MULTI mode, and showed the invocation was taken over by the latter program when in ALLOW_OVERRIDE mode.

Copy link

mergify bot commented Jul 29, 2024

@reyzell, this pull request is now in conflict and requires a rebase.

@reyzell
Copy link
Contributor Author

reyzell commented Jul 29, 2024

Updates: I've rebased from aya/main, and fixed clippy warnings on unused imports and an import style.

@reyzell
Copy link
Contributor Author

reyzell commented Jul 30, 2024

@dave-tucker I see the lint error due to changes to the public API [a]. Are there any actions I should take here?

[a] https://github.com/aya-rs/aya/actions/runs/10159730726/job/28109127559?pr=985

@tyrone-wu
Copy link
Contributor

It looks like you'll need to run cargo +nightly xtask public-api --bless to update the public API. The public API is tracked using a txt file.

@reyzell
Copy link
Contributor Author

reyzell commented Aug 5, 2024

Thanks @tyrone-wu, done. Blessed the public API changes.

Copy link

mergify bot commented Aug 5, 2024

Hey @alessandrod, this pull request changes the Aya Public API and requires your review.

@mergify mergify bot added the api/needs-review Makes an API change that needs review label Aug 5, 2024
@mergify mergify bot requested a review from alessandrod August 5, 2024 14:26
@mergify mergify bot added the test A PR that improves test cases or CI label Aug 5, 2024
@reyzell
Copy link
Contributor Author

reyzell commented Aug 7, 2024

Hello, @alessandrod and @dave-tucker, I'm checking in on the status of this one. Please let me know if I can help in any way.

@@ -28,6 +28,17 @@ pub trait Link: std::fmt::Debug + 'static {
fn detach(self) -> Result<(), ProgramError>;
}

bitflags::bitflags! {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this should be a bitflag vs a regular enum.

The kernel API is weird and uses bitflags, but say we wanted to add support for
BPF_F_REPLACE to attach, we'd have to provide the old program somehow, so it's
unlikely that we'd use attach() to do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see where you're going. I took a look, and I believe the use of bitflags is the more usable interface in this case. This allows the caller to easily set a combination of flags if they wish. The bitflags API frees the developer from having to do bit manipulation themselves.

If we later choose to add support for more flags, such as BPF_F_REPLACE, BPF_F_BEFORE, and BPF_F_AFTER, then we would add the ability to supply the bpf_attr anonymous struct's three additional fields of replace_bpf_fd, relative_fd, relative_id – those would be separate arguments. We would also add a way to set the BPF_F_ID flag to distinguish between the latter two.

I think the use of bitflags now is inline with where we would want to go.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the caller to easily set a combination of flags if they wish. The bitflags API frees the developer from having to do bit manipulation themselves.

My point is that I suspect we won't ever expose the bitflag to the aya API.

If we later choose to add support for more flags, such as BPF_F_REPLACE, BPF_F_BEFORE, and BPF_F_AFTER, then we would add the ability to supply the bpf_attr anonymous struct's three additional fields of replace_bpf_fd, relative_fd, relative_id – those would be separate arguments.

Exactly, similar to https://docs.aya-rs.dev/aya/programs/xdp/struct.xdp#method.attach_to_link where we use a REPLACE bit, but don't expose it to the API, see https://docs.aya-rs.dev/src/aya/programs/xdp.rs#233

Or in other words: you have to pass (flag, fd_or_id) and it's unlikely that we'll have a single method to do that, but more realistically we'll have attach_replace(old) attach_relative(AttachFlags::Before, fd) etc, where the bitflags become an implementation detail but are not exposed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are good points. From what I understand, you can also attach a program with both the ALLOW_MULTI and ALLOW_OVERRIDE flags set. A bitflags argument is useful there to allow the caller to specify empty, either flag, or set both flags. However, to keep the Aya API simple, we could go with your idea of an enum, and perhaps with a third value that represents both (ALLOW_MULTI_AND_OVERRIDE...?). If we want to avoid an EMPTY value on the enum, we could include both an attach and attach_with_flags function.

An alternative is to implement the flags argument as a struct with two booleans:

struct CgroupAttachFlags {
    allow_multi: bool,
    allow_override: bool,
}

A caller could then set either flag, both flags, or none. That's probably my favorite option, as it seems clean.

Do any of these options resonate with you?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from https://github.com/torvalds/linux/blob/35dfaad7188cdc043fde31709c796f5a692ba2bd/include/uapi/linux/bpf.h#L1094

 * BPF_F_ALLOW_OVERRIDE: If a sub-cgroup installs some bpf program,
 * the program in this cgroup yields to sub-cgroup program.
 *
 * BPF_F_ALLOW_MULTI: If a sub-cgroup installs some bpf program,
 * that cgroup program gets run in addition to the program in this cgroup.

so it seems like they're exclusive.

Given this and the docs of the other flags, I'm not convinced that at the aya level there's any need for bitflags, and bitflags are less idiomatic than regular enums so we should avoid them when possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, good instincts. I've updated this change to use an enum, and included a function scoped to the crate to translate the enum to its BPF value.

/// Attaches the program to the given cgroup.
///
/// The returned value can be used to detach, see [SockOps::detach].
pub fn attach_with_flags<T: AsFd>(&mut self, cgroup: T, flags: SockOpsFlags) -> Result<SockOpsLinkId, ProgramError> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it explicit which flags are being used.

I'm in two minds about this. For the Cgroup* and SockOps programs, I think it makes sense to have to pass CgroupAttachFlags to attach() - you're explicitly working with cgroups, so it makes sense to have to think about what's going on with other cgroups.

But for SkMsg SkSkb and LircMode2, I think we should default to no flags like before, and have a separate attach_with_flags() for the cases in which you do care about cgroups.

Copy link
Collaborator

@alessandrod alessandrod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this work!

Looks great, as per comment I suspect CgroupAttachFlags should be a regular enum, and some programs should have an explicit attach_with_flags, see #985 (comment)

@tyrone-wu
Copy link
Contributor

tyrone-wu commented Aug 19, 2024

I think I encountered this issue before. Try updating nightly and blessing public-api again.

@reyzell
Copy link
Contributor Author

reyzell commented Aug 19, 2024

I think I encountered this issue before. Try updating nightly and blessing public-api again.

Good call. The public API had changed after the last update of this PR, but before the workflow approval jobs had run. Thanks ;)

Copy link
Collaborator

@alessandrod alessandrod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good now, just not sure on the naming

@@ -28,6 +28,30 @@ pub trait Link: std::fmt::Debug + 'static {
fn detach(self) -> Result<(), ProgramError>;
}

/// Program attachment options.
#[derive(Clone, Copy, Debug, Default)]
pub enum CgroupAttachFlag {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: call this CgroupAttachMode?

/// Attaches the program to the given lirc device.
///
/// The returned value can be used to detach, see [LircMode2::detach].
pub fn attach_with_flag<T: AsFd>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and everywhere else: attach_with_cgroup_mode?

I'm not 100% convinced on mode, do you have a better name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, now onto the hard part, naming :)

So far I think you've picked a good name. Others I thought of are CgroupAttach- Strategy or Policy or Cardinality or Rule, they each have pros and cons. ("Strategy" is a bit generic, "Policy" is good but feels more security focused, "Cardinality" is accurate but a bit long, "Rule" I kind of like.)

If we go with any of these, I'm thinking of changing the name of the default enum value, as a mode of None sounds odd. Do these value names work?

pub enum CgroupAttachMode {
	Single,
	AllowMultiple,
	AllowOverride,
}

@alessandrod
Copy link
Collaborator

CI is failing because it needs rebase to pick up #1016

@reyzell reyzell changed the title Add support for multiple programs per cgroup Add the option to support multiple and overrideable programs per cgroup Aug 28, 2024
Copy link
Collaborator

@alessandrod alessandrod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there!

@@ -87,6 +88,7 @@ impl CgroupSkb {
&mut self,
cgroup: T,
attach_type: CgroupSkbAttachType,
flag: CgroupAttachMode,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and everywhere else: please rename the argument name from flag to mode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right! Done.

AllowMultiple,
}

impl CgroupAttachMode {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be impl From<CgroupAttachMode> for u32

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@alessandrod
Copy link
Collaborator

alessandrod commented Sep 2, 2024

I just checked in the kernel and these are the cgroup-aware programs:

	case BPF_PROG_TYPE_CGROUP_DEVICE:
	case BPF_PROG_TYPE_CGROUP_SKB:
	case BPF_PROG_TYPE_CGROUP_SOCK:
	case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
	case BPF_PROG_TYPE_CGROUP_SOCKOPT:
	case BPF_PROG_TYPE_CGROUP_SYSCTL:
	case BPF_PROG_TYPE_SOCK_OPS:
	case BPF_PROG_TYPE_LSM:
		if (ptype == BPF_PROG_TYPE_LSM &&
		    prog->expected_attach_type != BPF_LSM_CGROUP)
			ret = -EINVAL;
		else
			ret = cgroup_bpf_prog_attach(attr, ptype, prog);
		break;

For LIRC_MODE2 SK_MSG and SK_SKB you can't pass any of those flags, so we should remove attach_with_cgroup_mode from them.

See https://github.com/torvalds/linux/blob/35dfaad7188cdc043fde31709c796f5a692ba2bd/kernel/bpf/syscall.c#L3857

@reyzell
Copy link
Contributor Author

reyzell commented Sep 2, 2024

Ahh, thanks for catching that! I should have realized that. Fixed.

@reyzell
Copy link
Contributor Author

reyzell commented Sep 3, 2024

Applied two changes:

  • Added CgroupAttachMode to items re-exported from programs/mod.rs
  • Rebased from main

The following build steps pass:

cargo build --release
cargo +nightly xtask public-api --bless
cargo test --doc --all-features

@alessandrod
Copy link
Collaborator

Can you update your nightly and re-run cargo +nightly xtask public-api --bless?

@reyzell
Copy link
Contributor Author

reyzell commented Sep 3, 2024

Can you update your nightly and re-run cargo +nightly xtask public-api --bless?

Hmm, I made sure to do so before my last push (locally I'm up to date with the latest commit of 15eb935). I've done so again now and don't see anything that has changed. Something isn't clear to me about what's failing. I'll try to dig in...

@tyrone-wu
Copy link
Contributor

Hmm, interesting. What's your output for rustc +nightly -vV?

@reyzell
Copy link
Contributor Author

reyzell commented Sep 3, 2024

I have the following:

> rustc +nightly -vV
rustc 1.82.0-nightly (8b3870784 2024-08-07)
binary: rustc
commit-hash: 8b3870784f69d8ed2ae4246d1b56f150e3131499
commit-date: 2024-08-07
host: x86_64-unknown-linux-gnu
release: 1.82.0-nightly
LLVM version: 19.1.0

@tyrone-wu
Copy link
Contributor

It looks you'll need to update nightly again 😅. The latest is currently v1.83. Perhaps that'll resolve the public-api issue?

This change allows multiple BPF programs to attach to a cgroup (via the option
`CgroupAttachMode::AllowMultiple`), and allows a program to specify that it can be
overridden by one in a sub-cgroup (via the option `CgroupAttachMode::AllowOverride`).
@reyzell
Copy link
Contributor Author

reyzell commented Sep 4, 2024

You're right Tyron. Updated!

@alessandrod alessandrod merged commit 40f3032 into aya-rs:main Sep 5, 2024
26 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api/needs-review Makes an API change that needs review aya This is about aya (userspace) test A PR that improves test cases or CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants